#include <compiler/cpp/cpp_helpers.h>
#include <io/zero_copy_stream.h>
#include <io/printer.h>
#include <stubs/strutil.h>
#include <stubs/common.h>
#include <descriptor.h>
#include <descriptor.pb.h>

#include "cpp_styleguide.h"
#include <util/stream/output.h>

namespace NProtobuf {
namespace NCompiler {
namespace NPlugins {

    using namespace google::protobuf;
    using namespace google::protobuf::compiler;
    using namespace google::protobuf::compiler::cpp;

    typedef std::map<TProtoStringType, TProtoStringType> TVariables;

    void SetCommonFieldVariables(const FieldDescriptor* descriptor, TVariables* variables) {
        (*variables)["rname"] = descriptor->name();
        (*variables)["name"] = FieldName(descriptor);
        (*variables)["deprecation"] = descriptor->options().deprecated() ? " YPROTOBUF_DEPRECATED" : "";
#if 0
        /* doesn't work when there are different fields named xxx_yyy and XxxYyy
         * need to pass a per-message field registry to avoid overload clashes */
        TProtoStringType RName = descriptor->camelcase_name();
        RName.replace(0, 1, 1, toupper(RName[0]));
        if (descriptor->name() != RName)
            (*variables)["RName"] = RName;
#endif
    }

    TProtoStringType HeaderFileName(const FileDescriptor* file) {
        TProtoStringType basename = StripProto(file->name());

        return basename.append(".pb.h");
    }

    TProtoStringType SourceFileName(const FileDescriptor* file) {
        TProtoStringType basename = StripProto(file->name());

        return basename.append(".pb.cc");
    }

    bool HasProtobufPresenceChecker(const FieldDescriptor* field) {
        // returns true iff message has has_field_name() method
        if (field->file()->syntax() != FileDescriptor::SYNTAX_PROTO3) {
            return !field->is_repeated();
        } else {
            return !field->is_repeated() && (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE || field->containing_oneof() != NULL);
        }
    }

    bool IsLiteRuntimeMessage(const Descriptor* desc) {
        return desc->file() != NULL && desc->file()->options().optimize_for() == google::protobuf::FileOptions::LITE_RUNTIME;
    }

    bool IsAutogeneratedNestedType(const Descriptor* desc) {
        return desc->options().map_entry();
    }

    size_t NestedTypeCount(const Descriptor* desc) {
        size_t res = 0;

        for (int i = 0; i < desc->nested_type_count(); ++i) {
            res += !IsAutogeneratedNestedType(desc->nested_type(i));
        }

        return res;
    }

    class TFieldExtGenerator {
        public:
            TFieldExtGenerator(const FieldDescriptor* field)
                : Field_(field)
            {
                SetCommonFieldVariables(Field_, &Variables_);
            }

            virtual ~TFieldExtGenerator() throw () {
            }

            virtual void GenerateAccessorDeclarations(io::Printer* printer) = 0;
            virtual void GenerateJSONPrinting(io::Printer* printer) = 0;

        protected:
            void GenerateRepeatedJSONPrinting(io::Printer* printer, const char* itemPrinter) {
                printer->Print("out << '[';\n");
                printer->Print("{\n");
                printer->Indent();
                printer->Print("const char* separator = \"\";\n");
                printer->Print(Variables_, "for (size_t index = 0; index < $rname$Size(); ++index) {\n");
                printer->Indent();
                printer->Print("out << separator;\n");
                printer->Print(Variables_, itemPrinter);
                printer->Print(";\n");
                printer->Print("separator = \",\";\n");
                printer->Outdent();
                printer->Print("}\n");
                printer->Outdent();
                printer->Print("}\n");
                printer->Print("out << ']';\n");
            }

        protected:
            const FieldDescriptor* Field_;
            TVariables Variables_;
    };


    class TMessageFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TMessageFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["type"] = FieldMessageTypeName(Field_);

                printer->Print(Variables_,
                    "inline$deprecation$ const $type$& Get$rname$() const { return $name$(); }\n"
                    "inline$deprecation$ $type$* Mutable$rname$() { return mutable_$name$(); }\n");
                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ const $type$& Get$RName$() const { return $name$(); }\n"
                        "inline$deprecation$ $type$* Mutable$RName$() { return mutable_$name$(); }\n");
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                printer->Print(Variables_, "Get$rname$().PrintJSON(out);\n");
            }
    };

    class TMapFieldExtGenerator: public TFieldExtGenerator {
        public:
            TMapFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
                , Key_(field->message_type()->FindFieldByName("key"))
                , Val_(field->message_type()->FindFieldByName("value"))
            {
                Variables_["key_cpp"] = PrimitiveTypeName(Key_->cpp_type());

                switch (Val_->cpp_type()) {
                    case FieldDescriptor::CPPTYPE_MESSAGE:
                        Variables_["val_cpp"] = FieldMessageTypeName(Val_);
                        break;
                    case FieldDescriptor::CPPTYPE_ENUM:
                        Variables_["val_cpp"] = ClassName(Val_->enum_type(), true);
                        break;
                    default:
                        Variables_["val_cpp"] = PrimitiveTypeName(Val_->cpp_type());
                }
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                printer->Print(Variables_,
                    "inline$deprecation$ const ::google::protobuf::Map<$key_cpp$, $val_cpp$>& Get$rname$() const { return $name$(); }\n"
                    "inline$deprecation$ ::google::protobuf::Map<$key_cpp$, $val_cpp$>* Mutable$rname$() { return mutable_$name$(); }\n");
                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ const ::google::protobuf::Map<$key_cpp$, $val_cpp$>& Get$RName$() const { return $name$(); }\n"
                        "inline$deprecation$ ::google::protobuf::Map<$key_cpp$, $val_cpp$>* Mutable$RName$() { return mutable_$name$(); }\n");
            }

            void GenerateKeyValuePrinting(io::Printer* printer, const char* scopeName, bool isKey) {
                const FieldDescriptor* desc = isKey ? Key_ : Val_;

                switch(desc->cpp_type()) {
                    case FieldDescriptor::CPPTYPE_STRING:
                        printer->Print(~TString::Join("::google::protobuf::io::PrintJSONString(out, ", scopeName , ");\n"));
                        break;
                    case FieldDescriptor::CPPTYPE_ENUM:
                        printer->Print(~TString::Join("out << int(", scopeName, ");\n"));
                        break;
                    case FieldDescriptor::CPPTYPE_MESSAGE:
                        printer->Print(~TString::Join(scopeName, ".PrintJSON(out);\n"));
                        break;
                    default:
                        if (isKey) {
                            printer->Print(~TString::Join("out << '\"' << ", scopeName, " << '\"';\n"));
                        } else {
                            printer->Print(~TString::Join("out << ", scopeName, ";\n"));
                        }
                }
            }

            void GenerateJSONPrinting(io::Printer* printer) {
                printer->Print(Variables_,
                    "out << '{';\n"
                    "const ::google::protobuf::Map<$key_cpp$, $val_cpp$>& map = Get$rname$();\n"
                    "for (auto it = map.begin(); it != map.end(); ++it) {\n"
                );
                printer->Indent();
                printer->Print("if (it != map.begin()) { out << ','; }\n");

                GenerateKeyValuePrinting(printer, "it->first", true);
                printer->Print("out << ':';\n");
                GenerateKeyValuePrinting(printer, "it->second", false);

                printer->Outdent();
                printer->Print("}\n");
                printer->Print("out << '}';\n");
            }

        private:
            const FieldDescriptor* Key_;
            const FieldDescriptor* Val_;
    };

    class TRepeatedMessageFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TRepeatedMessageFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["type"] = FieldMessageTypeName(Field_);

                printer->Print(Variables_,
                    "inline$deprecation$ const $type$& Get$rname$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ $type$* Mutable$rname$(int index) { return mutable_$name$(index); }\n"
                    "inline$deprecation$ $type$* Add$rname$() { return add_$name$(); }\n"
                    "inline$deprecation$ const $type$& get_idx_$name$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedPtrField< $type$ >&\n"
                    "    get_arr_$name$() const { return $name$(); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedPtrField< $type$ >&\n"
                    "    Get$rname$() const { return $name$(); }\n"
                    "inline$deprecation$ ::google::protobuf::RepeatedPtrField< $type$ >*\n"
                    "    Mutable$rname$() { return mutable_$name$(); }\n");

                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ const $type$& Get$RName$(int index) const { return $name$(index); }\n"
                        "inline$deprecation$ $type$* Mutable$RName$(int index) { return mutable_$name$(index); }\n"
                        "inline$deprecation$ $type$* Add$RName$() { return add_$name$(); }\n"
                        "inline$deprecation$ const ::google::protobuf::RepeatedPtrField< $type$ >&\n"
                        "    Get$RName$() const { return $name$(); }\n"
                        "inline$deprecation$ ::google::protobuf::RepeatedPtrField< $type$ >*\n"
                        "    Mutable$RName$() { return mutable_$name$(); }\n"
                    );
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                GenerateRepeatedJSONPrinting(printer, "Get$rname$(index).PrintJSON(out)");
            }
    };

    class TStringFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TStringFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["pointer_type"] = Field_->type() == FieldDescriptor::TYPE_BYTES ? "void" : "char";

                if (Field_->options().ctype() != FieldOptions::STRING) {
                    printer->Outdent();
                    printer->Print(
                              " private:\n"
                              "  // Hidden due to unknown ctype option.\n");
                    printer->Indent();
                }

                printer->Print(Variables_,
                    "inline$deprecation$ const TProtoStringType& Get$rname$() const { return $name$(); }\n"
                    "inline$deprecation$ void Set$rname$(const TProtoStringType& value) { set_$name$(value); }\n"
                    "inline$deprecation$ void Set$rname$(const char* value) { set_$name$(value); }\n"
                    "inline$deprecation$ void Set$rname$(const $pointer_type$* value, size_t size) { set_$name$(value, size); }\n"
                    "inline$deprecation$ TProtoStringType* Mutable$rname$() { return mutable_$name$(); }\n");

                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ const TProtoStringType& Get$RName$() const { return $name$(); }\n"
                        "inline$deprecation$ void Set$RName$(const TProtoStringType& value) { set_$name$(value); }\n"
                        "inline$deprecation$ void Set$RName$(const char* value) { set_$name$(value); }\n"
                        "inline$deprecation$ void Set$RName$(const $pointer_type$* value, size_t size) { set_$name$(value, size); }\n"
                        "inline$deprecation$ TProtoStringType* Mutable$RName$() { return mutable_$name$(); }\n"
                    );

                if (Field_->options().ctype() != FieldOptions::STRING) {
                    printer->Outdent();
                    printer->Print(" public:\n");
                    printer->Indent();
                }

            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                printer->Print(Variables_, "::google::protobuf::io::PrintJSONString(out, Get$rname$());\n");
            }
    };

    class TRepeatedStringFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TRepeatedStringFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["pointer_type"] = Field_->type() == FieldDescriptor::TYPE_BYTES ? "void" : "char";

                if (Field_->options().ctype() != FieldOptions::STRING) {
                    printer->Outdent();
                    printer->Print(
                              " private:\n"
                              "  // Hidden due to unknown ctype option.\n");
                    printer->Indent();
                }

                printer->Print(Variables_,
                    "inline$deprecation$ const TProtoStringType& Get$rname$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ TProtoStringType* Mutable$rname$(int index) { return mutable_$name$(index); }\n"
                    "inline$deprecation$ void Set$rname$(int index, const TProtoStringType& value) { set_$name$(index, value); }\n"
                    "inline$deprecation$ void Set$rname$(int index, const char* value) { set_$name$(index, value); }\n"
                    "inline$deprecation$ void Set$rname$(int index, const $pointer_type$* value, size_t size) { set_$name$(index, value, size); }\n"
                    "inline$deprecation$ TProtoStringType* Add$rname$() { return add_$name$(); }\n"
                    "inline$deprecation$ void Add$rname$(const TProtoStringType& value) { add_$name$(value); }\n"
                    "inline$deprecation$ void Add$rname$(const char* value) { add_$name$(value); }\n"
                    "inline$deprecation$ void Add$rname$(const $pointer_type$* value, size_t size) { add_$name$(value, size); }\n"
                    "inline$deprecation$ const TProtoStringType& get_idx_$name$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedPtrField<TProtoStringType>& get_arr_$name$() const"
                    "{ return $name$(); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedPtrField<TProtoStringType>& Get$rname$() const"
                    "{ return $name$(); }\n"
                    "inline$deprecation$ ::google::protobuf::RepeatedPtrField<TProtoStringType>* Mutable$rname$()"
                    "{ return mutable_$name$(); }\n");

                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ const TProtoStringType& Get$RName$(int index) const { return $name$(index); }\n"
                        "inline$deprecation$ TProtoStringType* Mutable$RName$(int index) { return mutable_$name$(index); }\n"
                        "inline$deprecation$ void Set$RName$(int index, const TProtoStringType& value) { set_$name$(index, value); }\n"
                        "inline$deprecation$ void Set$RName$(int index, const char* value) { set_$name$(index, value); }\n"
                        "inline$deprecation$ void Set$RName$(int index, const $pointer_type$* value, size_t size) { set_$name$(index, value, size); }\n"
                        "inline$deprecation$ TProtoStringType* Add$RName$() { return add_$name$(); }\n"
                        "inline$deprecation$ void Add$RName$(const TProtoStringType& value) { add_$name$(value); }\n"
                        "inline$deprecation$ void Add$RName$(const char* value) { add_$name$(value); }\n"
                        "inline$deprecation$ void Add$RName$(const $pointer_type$* value, size_t size) { add_$name$(value, size); }\n"
                        "inline$deprecation$ const ::google::protobuf::RepeatedPtrField<TProtoStringType>& Get$RName$() const"
                        "{ return $name$(); }\n"
                        "inline$deprecation$ ::google::protobuf::RepeatedPtrField<TProtoStringType>* Mutable$RName$()"
                        "{ return mutable_$name$(); }\n"
                    );

                if (Field_->options().ctype() != FieldOptions::STRING) {
                    printer->Outdent();
                    printer->Print(" public:\n");
                    printer->Indent();
                }
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                GenerateRepeatedJSONPrinting(
                    printer,
                    "::google::protobuf::io::PrintJSONString(out, Get$rname$(index))"
                );
            }
    };

    class TEnumFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TEnumFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["type"] = ClassName(Field_->enum_type(), true);

                printer->Print(Variables_,
                    "inline$deprecation$ $type$ Get$rname$() const { return $name$(); } \n"
                    "inline$deprecation$ void Set$rname$($type$ value) { set_$name$(value); }\n");

                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ $type$ Get$RName$() const { return $name$(); } \n"
                        "inline$deprecation$ void Set$RName$($type$ value) { set_$name$(value); }\n"
                    );
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                printer->Print(Variables_, "out << (int)Get$rname$();\n");
            }
    };

    class TRepeatedEnumFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TRepeatedEnumFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["type"] = ClassName(Field_->enum_type(), true);

                printer->Print(Variables_,
                    "inline$deprecation$ $type$ Get$rname$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ void Set$rname$(int index, $type$ value) { set_$name$(index, value); }\n"
                    "inline$deprecation$ void Add$rname$($type$ value) { add_$name$(value); }\n"
                    "inline$deprecation$ $type$ get_idx_$name$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedField<int>& get_arr_$name$() const { return $name$(); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedField<int>& Get$rname$() const { return $name$(); }\n"
                    "inline$deprecation$ ::google::protobuf::RepeatedField<int>* Mutable$rname$() { return mutable_$name$(); }\n");
                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ $type$ Get$RName$(int index) const { return $name$(index); }\n"
                        "inline$deprecation$ void Set$RName$(int index, $type$ value) { set_$name$(index, value); }\n"
                        "inline$deprecation$ void Add$RName$($type$ value) { add_$name$(value); }\n"
                        "inline$deprecation$ const ::google::protobuf::RepeatedField<int>& Get$RName$() const { return $name$(); }\n"
                        "inline$deprecation$ ::google::protobuf::RepeatedField<int>* Mutable$RName$() { return mutable_$name$(); }\n"
                    );
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                GenerateRepeatedJSONPrinting(printer, "out << (int)Get$rname$(index)");
            }
    };

    class TPrimitiveFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TPrimitiveFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["type"] = PrimitiveTypeName(Field_->cpp_type());

                printer->Print(Variables_,
                    "inline$deprecation$ $type$ Get$rname$() const { return $name$();}\n"
                    "inline$deprecation$ void Set$rname$($type$ value) { set_$name$(value); }\n");
                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ $type$ Get$RName$() const { return $name$();}\n"
                        "inline$deprecation$ void Set$RName$($type$ value) { set_$name$(value); }\n"
                    );
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                printer->Print(Variables_, "out << Get$rname$();\n");
            }
    };

    class TRepeatedPrimitiveFieldExtGenerator: public TFieldExtGenerator  {
        public:
            TRepeatedPrimitiveFieldExtGenerator(const FieldDescriptor* field)
                : TFieldExtGenerator(field)
            {
            }

            void GenerateAccessorDeclarations(io::Printer* printer) {
                Variables_["type"] = PrimitiveTypeName(Field_->cpp_type());

                printer->Print(Variables_,
                    "inline$deprecation$ $type$ Get$rname$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ void Set$rname$(int index, $type$ value) { set_$name$(index, value); }\n"
                    "inline$deprecation$ void Add$rname$($type$ value) { add_$name$(value); }\n"
                    "inline$deprecation$ $type$ get_idx_$name$(int index) const { return $name$(index); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedField< $type$ >&\n"
                    "    get_arr_$name$() const { return $name$(); }\n"
                    "inline$deprecation$ const ::google::protobuf::RepeatedField< $type$ >&\n"
                    "    Get$rname$() const { return $name$(); }\n"
                    "inline$deprecation$ ::google::protobuf::RepeatedField< $type$ >*\n"
                    "    Mutable$rname$() { return mutable_$name$(); }\n");
                if (Variables_.end() != Variables_.find("RName"))
                    printer->Print(Variables_,
                        "inline$deprecation$ $type$ Get$RName$(int index) const { return $name$(index); }\n"
                        "inline$deprecation$ void Set$RName$(int index, $type$ value) { set_$name$(index, value); }\n"
                        "inline$deprecation$ void Add$RName$($type$ value) { add_$name$(value); }\n"
                        "inline$deprecation$ const ::google::protobuf::RepeatedField< $type$ >&\n"
                        "    Get$RName$() const { return $name$(); }\n"
                        "inline$deprecation$ ::google::protobuf::RepeatedField< $type$ >*\n"
                        "    Mutable$RName$() { return mutable_$name$(); }\n"
                    );
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                GenerateRepeatedJSONPrinting(printer, "out << Get$rname$(index)");
            }
    };

    class TBoolFieldExtGenerator: public TPrimitiveFieldExtGenerator {
        public:
            TBoolFieldExtGenerator(const FieldDescriptor* field)
                : TPrimitiveFieldExtGenerator(field)
            {
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                printer->Print(Variables_, "out << (Get$rname$() ? \"true\" : \"false\");\n");
            }
    };

    class TRepeatedBoolFieldExtGenerator: public TRepeatedPrimitiveFieldExtGenerator {
        public:
            TRepeatedBoolFieldExtGenerator(const FieldDescriptor* field)
                : TRepeatedPrimitiveFieldExtGenerator(field)
            {
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                GenerateRepeatedJSONPrinting(printer, "out << (Get$rname$(index) ? \"true\" : \"false\")");
            }
    };

    class TFloatFieldExtGenerator: public TPrimitiveFieldExtGenerator {
        public:
            TFloatFieldExtGenerator(const FieldDescriptor* field)
                : TPrimitiveFieldExtGenerator(field)
            {
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                printer->Print(Variables_, "out << double(Get$rname$());\n");
            }
    };

    class TRepeatedFloatFieldExtGenerator: public TRepeatedPrimitiveFieldExtGenerator {
        public:
            TRepeatedFloatFieldExtGenerator(const FieldDescriptor* field)
                : TRepeatedPrimitiveFieldExtGenerator(field)
            {
            }

            void GenerateJSONPrinting(io::Printer* printer) override {
                GenerateRepeatedJSONPrinting(printer, "out << double(Get$rname$(index))");
            }
    };

    // borrowed mostly from protobuf/compiler/cpp/cpp_extension.cc
    class TExtensionGenerator {
    public:
        TExtensionGenerator(const FieldDescriptor* descriptor)
            : Descriptor_(descriptor)
        {
            if (Descriptor_->is_repeated()) {
                type_traits_ = "Repeated";
            }

            TProtoStringType clsName;
            switch (Descriptor_->cpp_type()) {
            case FieldDescriptor::CPPTYPE_ENUM:
                type_traits_.append("EnumTypeTraits< ");
                clsName = ClassName(Descriptor_->enum_type(), true);
                type_traits_.append(clsName);
                type_traits_.append(", ");
                type_traits_.append(clsName);
                type_traits_.append("_IsValid>");
                break;
            case FieldDescriptor::CPPTYPE_STRING:
                type_traits_.append("StringTypeTraits");
                break;
            case FieldDescriptor::CPPTYPE_MESSAGE:
                type_traits_.append("MessageTypeTraits< ");
                type_traits_.append(ClassName(Descriptor_->message_type(), true));
                type_traits_.append(" >");
                break;
            default:
                type_traits_.append("PrimitiveTypeTraits< ");
                type_traits_.append(PrimitiveTypeName(Descriptor_->cpp_type()));
                type_traits_.append(" >");
                break;
            }
        }

        void GenerateDeclaration(io::Printer* printer) const
        {
            TVariables vars;
            vars["extendee"     ] = ClassName(Descriptor_->containing_type(), true);
            vars["type_traits"  ] = type_traits_;
            vars["name"         ] = Descriptor_->name();
            vars["field_type"   ] = SimpleItoa(static_cast<int>(Descriptor_->type()));
            vars["packed"       ] = Descriptor_->options().packed() ? "true" : "false";

            printer->Print(vars,
              "typedef ::google::protobuf::internal::ExtensionIdentifier< $extendee$,\n"
              "    ::google::protobuf::internal::$type_traits$, $field_type$, $packed$ >\n"
              "  Td$name$;\n"
              );
        }

    private:
        const FieldDescriptor* Descriptor_;
        TProtoStringType type_traits_;
    };

    class TOneofGenerator {
    public:
        TOneofGenerator(const OneofDescriptor* Descriptor_)
            : Descriptor_(Descriptor_)
        {
            Variables_["camel_oneof_name"] = UnderscoresToCamelCase(Descriptor_->name(), true);
            Variables_["rname"] = Descriptor_->name();
        }

        void GenerateDeclarations(io::Printer* printer) const {
            printer->Print(Variables_, "$camel_oneof_name$Case Get$rname$Case() const { return $rname$_case(); }\n");
            printer->Print(Variables_, "void Clear$rname$() { clear_$rname$(); }\n");
        }

    private:
        const OneofDescriptor* Descriptor_;
        TVariables Variables_;
    };

    TFieldExtGenerator* MakeGenerator(const FieldDescriptor* field) {
        if (field->is_repeated()) {
            switch (field->cpp_type()) {
                case FieldDescriptor::CPPTYPE_MESSAGE:
                    if (field->is_map()) {
                        return new TMapFieldExtGenerator(field);
                    }
                    return new TRepeatedMessageFieldExtGenerator(field);
                case FieldDescriptor::CPPTYPE_BOOL:
                    return new TRepeatedBoolFieldExtGenerator(field);
                case FieldDescriptor::CPPTYPE_FLOAT:
                    return new TRepeatedFloatFieldExtGenerator(field);
                case FieldDescriptor::CPPTYPE_STRING:
                    switch (field->options().ctype()) {
                        default:  // RepeatedStringFieldExtGenerator handles unknown ctypes.
                        case FieldOptions::STRING:
                            return new TRepeatedStringFieldExtGenerator(field);
                    }
                case FieldDescriptor::CPPTYPE_ENUM:
                    return new TRepeatedEnumFieldExtGenerator(field);
                default:
                    return new TRepeatedPrimitiveFieldExtGenerator(field);
            }
        } else {
            switch (field->cpp_type()) {
                case FieldDescriptor::CPPTYPE_MESSAGE:
                    return new TMessageFieldExtGenerator(field);
                case FieldDescriptor::CPPTYPE_BOOL:
                    return new TBoolFieldExtGenerator(field);
                case FieldDescriptor::CPPTYPE_FLOAT:
                    return new TFloatFieldExtGenerator(field);
                case FieldDescriptor::CPPTYPE_STRING:
                    switch (field->options().ctype()) {
                        default:  // StringFieldGenerator handles unknown ctypes.
                        case FieldOptions::STRING:
                            return new TStringFieldExtGenerator(field);
                    }
                case FieldDescriptor::CPPTYPE_ENUM:
                    return new TEnumFieldExtGenerator(field);
                default:
                    return new TPrimitiveFieldExtGenerator(field);
            }
        }
    }

    class TMessageExtGenerator {
        public:
            TMessageExtGenerator(const Descriptor* descriptor, OutputDirectory* outputDirectory)
                : Descriptor_(descriptor)
                , Classname_(ClassName(descriptor, false))
                , OutputDirectory_(outputDirectory)
                , FieldGenerators_(new scoped_ptr<TFieldExtGenerator>[descriptor->field_count()])
                , NestedTypeCount_(NestedTypeCount(descriptor))
                , NestedGenerators_(new scoped_ptr<TMessageExtGenerator>[NestedTypeCount_])
                , ExtensionGenerators_(new scoped_ptr<TExtensionGenerator>[descriptor->extension_count()])
                , OneofGenerators_(new scoped_ptr<TOneofGenerator>[descriptor->oneof_decl_count()])
            {
                for (int i = 0, idx = 0; i < descriptor->nested_type_count(); i++) {
                    if (!IsAutogeneratedNestedType(descriptor->nested_type(i))) {
                        NestedGenerators_[idx++].reset(new TMessageExtGenerator(descriptor->nested_type(i), OutputDirectory_));
                    }
                }

                for (int i = 0; i < descriptor->field_count(); i++) {
                    FieldGenerators_[i].reset(MakeGenerator(descriptor->field(i)));
                }

                for (int i = 0; i < descriptor->extension_count(); i++) {
                    ExtensionGenerators_[i].reset(new TExtensionGenerator(descriptor->extension(i)));
                }

                for (int i = 0; i < descriptor->oneof_decl_count(); i++) {
                    OneofGenerators_[i].reset(new TOneofGenerator(descriptor->oneof_decl(i)));
                }
            }

            void GenerateClassDefinitionExtension() {
                GenerateJSONImplementation();

                for (int i = 0; i < NestedTypeCount_; i++) {
                    NestedGenerators_[i]->GenerateClassDefinitionExtension();
                }
            }

            void GenerateDebugOutputExtension() {
                GenerateDebugOutput();

                for (int i = 0; i < NestedTypeCount_; i++) {
                    NestedGenerators_[i]->GenerateDebugOutputExtension();
                }
            }

            void GenerateClassExtension() {
                GenerateDebugStringImplementation();

                for (int i = 0; i < NestedTypeCount_; i++) {
                    NestedGenerators_[i]->GenerateClassExtension();
                }
            }

            void GenerateDeclarations() {
                GenerateFieldAccessorDeclarations();

                for (int i = 0; i < NestedTypeCount_; i++) {
                    NestedGenerators_[i]->GenerateDeclarations();
                }
            }

            void GenerateDefinitions() {
                GenerateClassExtension();
                GenerateDebugOutputExtension();
                GenerateClassDefinitionExtension();
            }

        private:
            void GenerateFieldAccessorDeclarations() {
                TProtoStringType fileName = HeaderFileName(Descriptor_->file());
                TProtoStringType scope = "class_scope:" + Descriptor_->full_name();
                scoped_ptr<io::ZeroCopyOutputStream> output(
                    OutputDirectory_->OpenForInsert(fileName, scope));
                io::Printer printer(output.get(), '$');

                printer.Print("// Yandex cpp-styleguide extension\n");
                for (int i = 0; i < Descriptor_->field_count(); i++) {
                    const FieldDescriptor* field = Descriptor_->field(i);

                    TVariables vars;
                    SetCommonFieldVariables(field, &vars);

                    const bool hasRName = (vars.end() != vars.find("RName"));
                    if (field->is_repeated()) {
                        printer.Print(vars,
                            "inline$deprecation$ size_t $rname$Size() const { return (size_t)$name$_size(); }\n");
                        if (hasRName)
                            printer.Print(vars,
                                "inline$deprecation$ size_t $RName$Size() const { return (size_t)$name$_size(); }\n");
                    } else if (HasProtobufPresenceChecker(field)) {
                        printer.Print(vars,
                            "inline$deprecation$ bool Has$rname$() const { return has_$name$(); }\n");
                        if (hasRName)
                            printer.Print(vars,
                                "inline$deprecation$ bool Has$RName$() const { return has_$name$(); }\n");
                    }

                    printer.Print(vars, "inline$deprecation$ void Clear$rname$() { clear_$name$(); }\n");
                    if (hasRName)
                        printer.Print(vars,
                            "inline$deprecation$ void Clear$RName$() { clear_$name$(); }\n");

                    // Generate type-specific accessor declarations.
                    FieldGenerators_[i]->GenerateAccessorDeclarations(&printer);

                    printer.Print("\n");
                }
                for (int i = 0; i < Descriptor_->extension_count(); i++) {
                    ExtensionGenerators_[i]->GenerateDeclaration(&printer);
                }
                for (int i = 0; i < Descriptor_->oneof_decl_count(); ++i) {
                    OneofGenerators_[i]->GenerateDeclarations(&printer);
                }
                TVariables vars;
                vars["class"] = ClassName(Descriptor_, false);
                if (!IsLiteRuntimeMessage(Descriptor_)) {
                    printer.Print("TProtoStringType ShortUtf8DebugString() const;\n");
                }
                printer.Print("void PrintJSON(TOutputStream&) const;\n");
                printer.Print(vars, "::google::protobuf::io::TAsJSON<$class$> AsJSON() const {\n");
                printer.Print(vars, "    return ::google::protobuf::io::TAsJSON<$class$>(*this);\n");
                printer.Print("}\n");
                printer.Print("// End of Yandex-specific extension\n");
            }

            void GenerateDebugStringImplementation() {
                TProtoStringType fileName = SourceFileName(Descriptor_->file());
                TProtoStringType scope = "namespace_scope";
                scoped_ptr<io::ZeroCopyOutputStream> output(
                    OutputDirectory_->OpenForInsert(fileName, scope));
                io::Printer printer(output.get(), '$');

                TVariables vars;
                vars["class"] = Classname_;
                if (!IsLiteRuntimeMessage(Descriptor_)) {
                    printer.Print("// Yandex-specific extension\n");
                    printer.Print(vars, "TProtoStringType $class$::ShortUtf8DebugString() const {\n");
                    printer.Print("    return ::ShortUtf8DebugString(*this);\n");
                    printer.Print("}\n");
                    printer.Print("// End of Yandex-specific extension\n");
                }
            }

            void GenerateJSONImplementation() {
                TProtoStringType fileName = SourceFileName(Descriptor_->file());
                TProtoStringType scope = "namespace_scope";
                scoped_ptr<io::ZeroCopyOutputStream> output(
                    OutputDirectory_->OpenForInsert(fileName, scope));
                io::Printer printer(output.get(), '$');
                printer.Print("// Yandex JSON extension\n");
                TVariables vars;
                vars["class"] = ClassName(Descriptor_, true);
                printer.Print(vars, "inline void $class$::PrintJSON(TOutputStream& out) const {\n");

                printer.Indent();
                printer.Print("out << '{';\n");
                if (Descriptor_->field_count() > 0) {
                    printer.Print("const char* sep = \"\";\n");
                }
                for (int i = 0; i < Descriptor_->field_count(); i++) {
                    const FieldDescriptor* field = Descriptor_->field(i);

                    TVariables vars;
                    SetCommonFieldVariables(field, &vars);

                    if (field->is_repeated()) {
                        // map or repeated field in both proto3 and proto2 syntax
                        printer.Print(vars, "if ($rname$Size() > 0) {\n");
                    } else if (HasProtobufPresenceChecker(field)) {
                        // any optional or required field in proto2 syntax
                        // message-field or any oneof field in proto3 syntax
                        printer.Print(vars, "if (Has$rname$()) {\n");
                    } else {
                        // string, enum or primitive field in proto3 syntax
                        printer.Print(vars, "if (Get$rname$()) {\n");
                    }

                    printer.Indent();
                    printer.Print("out << sep;\n");
                    printer.Print(vars, "out << \"\\\"$rname$\\\":\";\n");
                    FieldGenerators_[i]->GenerateJSONPrinting(&printer);
                    printer.Print(vars, "sep = \",\";\n");
                    printer.Outdent();
                    printer.Print("}\n");
                }
                printer.Print("out << '}';\n");
                printer.Outdent();
                printer.Print("}\n");

                printer.Print("// End of Yandex JSON extension\n");
            }

            void GenerateDebugOutput() {
                TProtoStringType fileName = SourceFileName(Descriptor_->file());
                TProtoStringType scope = "global_scope";
                scoped_ptr<io::ZeroCopyOutputStream> output(
                    OutputDirectory_->OpenForInsert(fileName, scope));
                io::Printer printer(output.get(), '$');
                if (!IsLiteRuntimeMessage(Descriptor_)) {
                    printer.Print("// Yandex debug output extension\n");
                    TVariables vars;
                    vars["class"] = ClassName(Descriptor_, true);
                    printer.Print("template<>\n");
                    printer.Print(vars, "void Out< $class$>(TOutputStream& out, const $class$& msg) {\n");
                    printer.Print("    out << \"{ \" << msg.ShortUtf8DebugString() << \" }\";\n");
                    printer.Print("}\n");
                    printer.Print("// End of Yandex debug output extension\n");
                }
            }

        private:
            const Descriptor* Descriptor_;
            const TProtoStringType Classname_;
            OutputDirectory* OutputDirectory_;
            scoped_array<scoped_ptr<TFieldExtGenerator> >  FieldGenerators_;
            const size_t NestedTypeCount_;
            scoped_array<scoped_ptr<TMessageExtGenerator> > NestedGenerators_;
            scoped_array<scoped_ptr<TExtensionGenerator> > ExtensionGenerators_;
            scoped_array<scoped_ptr<TOneofGenerator> > OneofGenerators_;
    };

    class TFileExtGenerator {
        public:
            TFileExtGenerator(const FileDescriptor* file, OutputDirectory* output_directory)
                : File_(file)
                , OutputDirectory_(output_directory)
                , MessageTypeCount_(file->message_type_count())
                , MessageGenerators_(new scoped_ptr<TMessageExtGenerator>[MessageTypeCount_])
            {
                for (size_t i = 0; i < MessageTypeCount_; i++) {
                    MessageGenerators_[i].reset(new TMessageExtGenerator(file->message_type(i), OutputDirectory_));
                }
            }

            void GenerateHeaderExtensions() {
                GenerateHeaderIncludeExtensions();

                for (size_t i = 0; i < MessageTypeCount_; i++) {
                    MessageGenerators_[i]->GenerateDeclarations();
                }
            }

            void GenerateSourceExtensions() {
                GenerateSourceIncludeExtensions();

                for (size_t i = 0; i < MessageTypeCount_; i++) {
                    MessageGenerators_[i]->GenerateDefinitions();
                }
            }

        private:
            void GenerateSourceIncludeExtensions() {
                TProtoStringType fileName = SourceFileName(File_);
                TProtoStringType scope = "includes";
                scoped_ptr<io::ZeroCopyOutputStream> output(
                    OutputDirectory_->OpenForInsert(fileName, scope));
                io::Printer printer(output.get(), '$');
                printer.Print("#include <contrib/libs/protobuf/messagext.h>\n");
            }

            void GenerateHeaderIncludeExtensions() {
                TProtoStringType fileName = HeaderFileName(File_);
                TProtoStringType scope = "includes";
                scoped_ptr<io::ZeroCopyOutputStream> output(
                    OutputDirectory_->OpenForInsert(fileName, scope));
                io::Printer printer(output.get(), '$');
                printer.Print("#include <contrib/libs/protobuf/json_util.h>\n");
            }

        private:
            const FileDescriptor* File_;
            OutputDirectory* OutputDirectory_;
            size_t MessageTypeCount_;
            scoped_array<scoped_ptr<TMessageExtGenerator> > MessageGenerators_;
    };

    bool TCppStyleGuideExtensionGenerator::Generate(const FileDescriptor* file,
        const TProtoStringType&,
        OutputDirectory* outputDirectory,
        TProtoStringType*) const {

        TFileExtGenerator fileGenerator(file, outputDirectory);

        // Generate header.
        fileGenerator.GenerateHeaderExtensions();

        // Generate cc file.
        fileGenerator.GenerateSourceExtensions();

        return true;
    }

}
}
}

int main(int argc, char* argv[]) {
#ifdef _MSC_VER
    // Don't print a silly message or stick a modal dialog box in my face,
    // please.
    _set_abort_behavior(0, ~0);
#endif  // !_MSC_VER

    NProtobuf::NCompiler::NPlugins::TCppStyleGuideExtensionGenerator generator;
    return google::protobuf::compiler::PluginMain(argc, argv, &generator);
}
